using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Nop.Core;
using Nop.Core.Data;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Media;
using Nop.Services.Configuration;
using Nop.Services.Events;
using Nop.Services.Logging;
using Nop.Services.Seo;
using Microsoft.WindowsAzure.Storage;
using System.Configuration;
using Microsoft.WindowsAzure.Storage.Blob;
using Nop.Services.Helpers;

namespace Nop.Services.Media
{
    public class BlobStorePictureService : PictureService
    {
        private readonly IRepository<Picture> _pictureRepository;
        private readonly ISettingService _settingService;
        private readonly ILogger _logger;
        private readonly IEventPublisher _eventPublisher;
        private readonly MediaSettings _mediaSettings;

        private CloudBlobClient blobClient;
        private CloudBlobContainer container;
        private CloudBlobContainer uploadContainer;

        public BlobStorePictureService(IRepository<Picture> pictureRepository,
            IRepository<ProductPicture> productPictureRepository,
            ISettingService settingService, IWebHelper webHelper,
            ILogger logger, IEventPublisher eventPublisher,
            MediaSettings mediaSettings) :
            base(pictureRepository, productPictureRepository, settingService, webHelper, logger, eventPublisher, mediaSettings)
        {
            this._pictureRepository = pictureRepository;
            this._settingService = settingService;
            this._logger = logger;
            this._eventPublisher = eventPublisher;
            this._mediaSettings = mediaSettings;

            blobClient = BlobStorageHelper.GetBlobClient(_mediaSettings.BlobStorageConnectionString);
            container = BlobStorageHelper.GetContainer(blobClient, _mediaSettings.ProductImageContainer);
            uploadContainer = BlobStorageHelper.GetContainer(blobClient, _mediaSettings.UploadImageContainer);
        }

        public override string GetDefaultPictureUrl(int targetSize = 0, Core.Domain.Media.PictureType defaultPictureType = PictureType.Entity, bool? useSsl = null)
        {
            string defaultImageName;
            switch (defaultPictureType)
            {
                case PictureType.Entity:
                    defaultImageName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.gif");
                    break;
                case PictureType.Avatar:
                    defaultImageName = _settingService.GetSettingByKey("Media.Customer.DefaultAvatarImageName", "default-avatar.jpg");
                    break;
                default:
                    defaultImageName = _settingService.GetSettingByKey("Media.DefaultImageName", "default-image.gif");
                    break;
            }

            string relPath = ImageUrlUtil.GetImageAccessUrl(defaultImageName, useSsl, _mediaSettings.ImageHost, container.Name);
            if (targetSize == 0)
                return relPath;

            var picReference = container.GetBlockBlobReference(defaultImageName);
            if (picReference.Exists())
            {
                string fileExtension = Path.GetExtension(defaultImageName);
                string fname = string.Format("{0}_{1}{2}",
                    Path.GetFileNameWithoutExtension(defaultImageName),
                    targetSize,
                    fileExtension);
                var thumbRef = container.GetBlockBlobReference(this.LocalThumbImagePath + fname);
                if (!thumbRef.Exists())
                {
                    using (var b = new Bitmap(picReference.OpenRead()))
                    {
                        var newSize = ImageUtil.CalculateDimensions(b.Size, targetSize);

                        if (newSize.Width < 1)
                            newSize.Width = 1;
                        if (newSize.Height < 1)
                            newSize.Height = 1;
                        var bytes = ImageUtil.DoCreateImage(fileExtension, b, newSize, this.ImageQuality);
                        thumbRef.UploadFromByteArray(bytes, 0, bytes.Length);
                    }
                }
                return ImageUrlUtil.GetImageAccessUrl(this.LocalThumbImagePath + fname, useSsl, _mediaSettings.ImageHost, container.Name);
            }
            return relPath;
        }

        public override byte[] LoadPictureFromFile(int pictureId, string mimeType)
        {
            string lastPart = ImageUrlUtil.GetFileExtensionFromMimeType(mimeType);
            var picture = GetPictureById(pictureId);
            string azureFilename;
            if (!String.IsNullOrEmpty(picture.FileName))
                azureFilename = string.Format("{0}_0.{1}", picture.FileName, lastPart);
            else
                azureFilename = string.Format("{0}_0.{1}", ImageUrlUtil.FormatId(pictureId), lastPart);
            var blobRef = container.GetBlockBlobReference(azureFilename);
            if (blobRef.Exists())
            {
                blobRef.FetchAttributes();
                long fileByteLength = blobRef.Properties.Length;
                byte[] fileContent = new byte[fileByteLength];
                for (int i = 0; i < fileByteLength; i++)
                {
                    fileContent[i] = 0x20;
                }

                blobRef.DownloadToByteArray(fileContent, 0);
                return fileContent;
            }
            return null;
        }

        public override string GetPictureUrl(Core.Domain.Media.Picture picture, int targetSize = 0, bool showDefaultPicture = true, bool? useSsl = null, bool isWater = false)
        {
            string url = string.Empty;
            if (picture == null)
            {
                if (showDefaultPicture)
                    url = GetDefaultPictureUrl(targetSize, useSsl: useSsl);
                return url;
            }
            var fileName = ImageUrlUtil.GetImageFileName(picture, 0, isWater);

            var blobFile = BlobStorageHelper.GetBlockBlob(container, fileName);
            if (!blobFile.Exists())
            {
                if (showDefaultPicture)
                {
                    url = GetDefaultPictureUrl(targetSize, useSsl: useSsl);
                }
                return url;
            }

            if (targetSize == 0)
            {
                return ImageUrlUtil.GetImageAccessUrl(blobFile.Name, useSsl, _mediaSettings.ImageHost, container.Name);
            }

            var thumbFileName = ImageUrlUtil.GetThumbImageFileName(picture, targetSize);
            var thumbBlobRef = container.GetBlockBlobReference(this.LocalThumbImagePath + thumbFileName);

            if (!thumbBlobRef.Exists())
            {
                var thumbUrl = GenerateThumbImage(blobFile, thumbBlobRef, picture.Id, targetSize,
                    ImageUrlUtil.GetFileExtensionFromMimeType(picture.MimeType), useSsl);
                if (string.IsNullOrEmpty(thumbUrl))
                {
                    return GetDefaultPictureUrl(targetSize, useSsl: useSsl);
                }
            }

            return ImageUrlUtil.GetImageAccessUrl(thumbBlobRef.Name, useSsl, _mediaSettings.ImageHost, container.Name);
        }

        private string GenerateThumbImage(CloudBlockBlob sourceBlob, CloudBlockBlob destBlob, int id, int targetSize,
            string extension, bool? useSSL)
        {
            byte[] imageBytes = null;
            using (var stream = new MemoryStream())
            {
                sourceBlob.DownloadToStream(stream);
                imageBytes = stream.ToArray();
            }
            if (imageBytes.Length == 0)
            {
                return null;
            }

            Bitmap b = null;
            using (var stream = new MemoryStream(imageBytes))
            {
                try
                {
                    //try-catch to ensure that picture binary is really OK. Otherwise, we can get "Parameter is not valid" exception if binary is corrupted for some reasons
                    b = new Bitmap(stream);
                }
                catch (ArgumentException exc)
                {
                    _logger.Error(string.Format("Error generating picture thumb. ID={0}", id), exc);
                }
                if (b == null)
                {
                    return null;
                }
                var newSize = ImageUtil.CalculateDimensions(b.Size, targetSize);

                if (newSize.Width < 1)
                    newSize.Width = 1;
                if (newSize.Height < 1)
                    newSize.Height = 1;

                byte[] bytes = ImageUtil.DoCreateImage(extension, b, newSize, this.ImageQuality);
                destBlob.UploadFromByteArray(bytes, 0, bytes.Length);
            }
            b.Dispose();

            return ImageUrlUtil.GetImageAccessUrl(destBlob.Name, useSSL, _mediaSettings.ImageHost, container.Name);
        }

        protected override void DeletePictureThumbs(Picture picture)
        {
            string filter;
            if (!String.IsNullOrEmpty(picture.FileName))
                filter = this.LocalThumbImagePath + picture.FileName;
            else
                filter = this.LocalThumbImagePath + ImageUrlUtil.FormatId(picture.Id);
            var blobs = container.ListBlobs(filter, true, BlobListingDetails.None);
            foreach (var blob in blobs)
            {
                var url = blob.Uri.ToString();
                var index = url.IndexOf(container.Name);
                var blobName = url.Substring(index + container.Name.Length + 1);
                var blobRef = container.GetBlockBlobReference(this.LocalThumbImagePath + blobName);
                blobRef.DeleteIfExists();
            }
        }

        public override void DeletePicture(Core.Domain.Media.Picture picture)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            //delete thumbs
            DeletePictureThumbs(picture);

            //delete from file system
            if (!this.StoreInDb)
                DeletePictureOnFileSystem(picture);

            //delete from database
            _pictureRepository.Delete(picture);

            //event notification
            _eventPublisher.EntityDeleted(picture);
        }

        protected override void SavePictureInFile(Picture picture, byte[] pictureBinary, string mimeType, bool isWater = false)
        {
            SavePicturePri(picture, pictureBinary);
            SavePicturePri(picture, WarterImageMark(pictureBinary), true);
        }

        private void SavePicturePri(Picture picture, byte[] pictureBinary, bool isWater = false)
        {

            if (pictureBinary == null||pictureBinary.Length<=0) return;

            var localFilename = ImageUrlUtil.GetImageFileName(picture, 0, isWater);

            var blobRef = container.GetBlockBlobReference(localFilename);
            blobRef.UploadFromByteArray(pictureBinary, 0, pictureBinary.Length);

        }

        public byte[] WarterImageMark(byte[] pictureBinary, string watermark = "SportsHUB", Locative locative = Locative.RightDown)
        {
            if (pictureBinary == null) return null;
            using (Stream stream = new MemoryStream(pictureBinary))
            {
                Image img = new Bitmap(stream);
                return ImageUtil.ImageWaterMarkText(img, watermark, locative, this.ImageQuality);
            }
        }

        public override Picture UpdatePicture(int pictureId, byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool validateBinary = true)
        {
            mimeType = CommonHelper.EnsureNotNull(mimeType);
            mimeType = CommonHelper.EnsureMaximumLength(mimeType, 20);

            seoFilename = CommonHelper.EnsureMaximumLength(seoFilename, 100);

            //avoid validation error
            if (validateBinary && pictureBinary != null && pictureBinary.Length > 0)
                pictureBinary = ValidatePicture(pictureBinary, mimeType);

            var picture = GetPictureById(pictureId);
            if (picture == null||pictureBinary == null || pictureBinary.Length <= 0)
                return null;

            //delete old thumbs if a picture has been changed
            if (seoFilename != picture.SeoFilename)
                DeletePictureThumbs(picture);

            picture.PictureBinary = (this.StoreInDb ? pictureBinary : new byte[0]);
            picture.MimeType = mimeType;
            picture.SeoFilename = seoFilename;
            picture.IsNew = isNew;
            picture.FileName = picture.Id.ToString("D10") + "-" + (DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond).ToString();

            _pictureRepository.Update(picture);

            if (!this.StoreInDb) //Problem when update, the SEO file name not passed in
                SavePictureInFile(picture, pictureBinary, mimeType);

            //event notification
            _eventPublisher.EntityUpdated(picture);

            return picture;
        }

        /// <summary>
        /// Loads the QR Code picture (of a Logistics Package) from file
        /// Assumes that a QR Code picture is contained in a file located at LocalImagePath.
        /// Assumes that this file was somewhat already created via another function call!!
        /// </summary>
        /// <param name="packageId">Package ID</param>
        /// <param name="mimeType">MIME type</param>
        /// <returns>QR Code picture binary</returns>
        public override byte[] LoadLogisticsPackageQrCodePictureFromFile(int packageId, string mimeType)
        {
            string lastPart = ImageUrlUtil.GetFileExtensionFromMimeType(mimeType);
            string localFilename = string.Format("Package__QRCode__{0}_0.{1}", ImageUrlUtil.FormatId(packageId), lastPart);
            return BlobStorageHelper.GetBlobBytes(container, localFilename);
        }

        /// <summary>
        /// Get a URL pointing to a Logistics Package's QR Code
        /// </summary>
        /// <param name="packageId">Package ID</param>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <param name="showDefaultPicture">A value indicating whether the default picture is shown</param>
        /// <param name="useSsl">Value indicating whether to get SSL protected picture URL; null to use the same value as the current page</param>
        /// <returns>QR Code picture URL</returns>
        public override string GetLogisticsPackageQrCodeUrl(int packageId, int targetSize = 0, bool showDefaultPicture = true, bool? useSsl = null)
        {
            const string qrCodeMimeType = "image/jpeg";
            string url = string.Empty;

            if (packageId < 1)
                return url;

            byte[] pictureBinary = null;
            pictureBinary = LoadLogisticsPackageQrCodePictureFromFile(packageId, qrCodeMimeType);

            // I assume this is a generic "stub" picture, so just return something like this even for QR Code.
            if (pictureBinary == null || pictureBinary.Length == 0)
            {
                if (showDefaultPicture)
                {
                    url = GetDefaultPictureUrl(targetSize, useSsl: useSsl);
                }
                return url;
            }

            string lastPart = ImageUrlUtil.GetFileExtensionFromMimeType(qrCodeMimeType);
            string localFilename;


            if (targetSize == 0)
            {
                localFilename = string.Format("Package__QRCode__{0}_0.{1}", ImageUrlUtil.FormatId(packageId), lastPart);

                var blobRef = BlobStorageHelper.GetBlockBlob(container,
                        this.LocalThumbImagePath + localFilename);
                if (!blobRef.Exists())
                {
                    blobRef.UploadFromByteArray(pictureBinary, 0, pictureBinary.Length);
                }
                return ImageUrlUtil.GetImageAccessUrl(this.LocalThumbImagePath + localFilename, useSsl, _mediaSettings.ImageHost, container.Name);
            }
            else
            {
                localFilename = string.Format("Package__QRCode__{0}_0.{1}.{2}", ImageUrlUtil.FormatId(packageId), targetSize, lastPart);
                var thumbQrFile = BlobStorageHelper.GetBlockBlob(container, this.LocalThumbImagePath + localFilename);
                if (!thumbQrFile.Exists())
                {
                    using (var stream = new MemoryStream(pictureBinary))
                    {
                        Bitmap b = null;
                        try
                        {
                            //try-catch to ensure that picture binary is really OK. Otherwise, we can get "Parameter is not valid" exception if binary is corrupted for some reasons
                            b = new Bitmap(stream);
                        }
                        catch (ArgumentException exc)
                        {
                            _logger.Error(string.Format("Error generating picture thumb for QR Code of Package ID={0}", packageId), exc);
                        }
                        if (b == null)
                        {
                            //bitmap could not be loaded for some reasons
                            return url;
                        }
                        var newSize = ImageUtil.CalculateDimensions(b.Size, targetSize);

                        if (newSize.Width < 1)
                            newSize.Width = 1;
                        if (newSize.Height < 1)
                            newSize.Height = 1;

                        var newImageBytes = ImageUtil.DoCreateImage(lastPart, b, newSize, this.ImageQuality);
                        thumbQrFile.UploadFromByteArray(newImageBytes, 0, newImageBytes.Length);
                    }
                }
                return ImageUrlUtil.GetImageAccessUrl(localFilename, useSsl, _mediaSettings.ImageHost, container.Name);
            }
        }

        /// <summary>
        /// Get the local path to a Package's QR Code picture file
        /// </summary>
        /// <param name="packageId">Package Id</param>
        /// <param name="targetSize">The target picture size (longest side)</param>
        /// <param name="showDefaultPicture">A value indicating whether the default picture is shown</param>
        /// <returns></returns>
        public override string GetPackageQrCodePictureLocalPath(int packageId, int targetSize = 0, bool showDefaultPicture = true)
        {
            string url = GetLogisticsPackageQrCodeUrl(packageId, targetSize, showDefaultPicture);
            if (String.IsNullOrEmpty(url))
                return String.Empty;
            else
                return this.LocalThumbImagePath + Path.GetFileName(url);
        }

        public override void Migrate()
        {
            Migrate(this.LocalImagePath);
        }

        private void Migrate(string sourceDir)
        {
            var files = System.IO.Directory.GetFiles(sourceDir, "*.*", SearchOption.TopDirectoryOnly);
            Array.ForEach(files, file =>
            {
                byte[] bytes = File.ReadAllBytes(file);
                var fileName = Path.GetFileNameWithoutExtension(file);
                BlobStorageHelper.CopyLocalFileToBlobStorage(container, bytes, fileName);
            });
        }

        protected override void DeletePictureOnFileSystem(Picture picture)
        {
            if (picture == null)
                throw new ArgumentNullException("picture");

            string lastPart = ImageUrlUtil.GetFileExtensionFromMimeType(picture.MimeType);
            string id;
            if (!String.IsNullOrEmpty(picture.FileName))
                id = picture.FileName;
            else
                id = ImageUrlUtil.FormatId(picture.Id);
            string filename = ImageUrlUtil.GetPictureNameNoSeo(ImageUrlUtil.ThumbImageNameFormat, id, 0, lastPart);
            string fileWaterMarkName = ImageUrlUtil.GetPictureNameNoSeo(ImageUrlUtil.WatermarkThumbImageNameFormat, id,
                0, lastPart);

            DeletePicturePri(filename);
            DeletePicturePri(fileWaterMarkName);
        }

        private void DeletePicturePri(string filename)
        {
            var blobRef = container.GetBlockBlobReference(filename);
            blobRef.DeleteIfExists();
        }

        /// <summary>
        /// Gets a local thumb image path
        /// </summary>
        protected override string LocalThumbImagePath
        {
            get
            {
				return "";
            }
        }

        #region Picture Manager

        public override List<IListBlobItem> ListFolderContent(string folder, CloudBlobContainer currentContainer, BlobContinuationToken currentToken, out BlobContinuationToken nextToken)
        {
            if (currentContainer == null)
                currentContainer = uploadContainer;
            nextToken = new BlobContinuationToken();
            if (String.IsNullOrEmpty(folder))
            {
                folder = null;
                var blobs = currentContainer.ListBlobsSegmented(folder, false, BlobListingDetails.Metadata, 1000, currentToken, null, null);
                nextToken = blobs.ContinuationToken;
                return blobs.Results.ToList();
            }
            else
            {
                var folderRef = currentContainer.GetDirectoryReference(@folder);
                var blobs = folderRef.ListBlobsSegmented(false, BlobListingDetails.Metadata, 1000, currentToken, null, null);
                nextToken = blobs.ContinuationToken;
                return blobs.Results.ToList();
            }
            
        }

        public override bool SaveUploadedPictureToFolder(string folder, byte[] fileBinary, CloudBlobContainer currentContainer, string fileName, string contentType, out CloudBlockBlob savedFile)
        {
            if (currentContainer == null)
                currentContainer = uploadContainer;
            if (String.IsNullOrEmpty(folder))
                folder = "";
            else
                folder = folder + "/";

            savedFile = null;

            if (fileBinary == null || fileBinary.Length <= 0 || String.IsNullOrEmpty(fileName) || String.IsNullOrEmpty(contentType)) return false;

            var extension = Path.GetExtension(fileName);
            var fileNameNoExt = Path.GetFileNameWithoutExtension(fileName);
            var saveName = folder + fileNameNoExt + extension;
            var blobRef = currentContainer.GetBlockBlobReference(saveName);
            var i = 0;
            while (blobRef.Exists())
            {
                saveName = folder + fileNameNoExt + "_" + i.ToString() + extension;
                blobRef = currentContainer.GetBlockBlobReference(saveName);
            }
            var ms = new MemoryStream(fileBinary);
            var b = new Bitmap(ms);
            blobRef.Properties.ContentType = contentType;
            blobRef.Metadata.Add("width", b.Width.ToString());
            blobRef.Metadata.Add("height", b.Height.ToString());
            blobRef.UploadFromByteArray(fileBinary, 0, fileBinary.Length);
            // Thumb
            var thumbRef = currentContainer.GetBlockBlobReference("Thumb/" + saveName);
            using (b)
            {
                var size = ImageUtil.CalculateDimensions(b.Size, 50);
                var thumbBinary = ImageUtil.DoCreateImage(extension, b, size, this.ImageQuality);
                thumbRef.Properties.ContentType = contentType;
                thumbRef.UploadFromByteArray(thumbBinary, 0, thumbBinary.Length);
            }
            savedFile = blobRef;
            return true;
        }

        public override bool DeleteUploadedImage(string path)
        {
            var currentContainer = uploadContainer;
            if (String.IsNullOrEmpty(path)) return false;
            var blobRef = currentContainer.GetBlockBlobReference(path);
            var thumbRef = currentContainer.GetBlockBlobReference("Thumb/" + path);
            return blobRef.DeleteIfExists() && thumbRef.DeleteIfExists();
        }

        public override bool CreateFolder(string path, out CloudBlobDirectory folder)
        {
            folder = null;
            var currentContainer = uploadContainer;
            if (String.IsNullOrEmpty(path)) return false;
            var folderRef = currentContainer.GetBlockBlobReference(path + "/dir.config");
            folderRef.UploadText("");
            folder = folderRef.Parent;
            return true;
        }
        public override bool DeleteFolder(string path)
        {
            var currentContainer = uploadContainer;
            if (String.IsNullOrEmpty(path)) return false;
            var blobs = currentContainer.ListBlobs(path);
            foreach (var blob in blobs.Where((b, type) => b is CloudBlockBlob))
            {
                var blockBlob = blob as CloudBlockBlob;
                var blobRef = currentContainer.GetBlockBlobReference(blockBlob.Name);
                blobRef.DeleteIfExists();
            }
            return true;
        }

        #endregion
    }
}
